/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.common.utils.web.model;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.Model;
import org.springframework.util.Assert;

import com.foreveross.bsl.common.utils.web.QueryFilter;
import com.foreveross.bsl.common.utils.web.QuerySpecs;
import com.foreveross.bsl.common.utils.web.Servlets;
import com.foreveross.bsl.common.utils.web.QueryOrder;
import com.google.common.collect.Maps;

/**
 * 辅助Controller类处理QuerySpecs相关的model<br/>
 * 使用简单查询组件涉及步骤：<br/>
 * <pre>
 * 1. 在service层的接口定义中，相关的方法使用{@code}QuerySpecs{@code}定义参数
 *   （如果该接口是WebService接口且为GET方法,则要声明@QueryParam，并要在WS客户端配置配置ParamConverter）
 *    可参考push-application项目的PushMgmtService类。
 * 2. 在service层的接口实现中，调用repository.findByQuerySpecs()方法实现具体查询。
 *    可参考push-application-impl项目的DefaultPushMgmtService类。
 * 3. 在web controller层，查询的方法要定义Model和HttpServletRequest类型的参数，在方法内创建QueryModelHelper对象处理查询和排序参数。
 *    可参考bsl-web项目的PushRequestQueryController类
 * 4. 在页面，引入simplequery.part.html，参考bsl-web项目的/webapp/push/request-list.html页面
 * </pre>
 * @author Wangyi
 * @version v1.0
 *
 * @date 2013-9-24
 * @see QuerySpecs
 */
public class QueryModelHelper {
	
	private static final String DEFAULT_QUERY_PREFIX="q_";
	private static final String DEFAULT_SORT_PREFIX="st_";
	private String queryParamsPrefix=DEFAULT_QUERY_PREFIX;
	private String sortParamsPrefix=DEFAULT_SORT_PREFIX;
	private final String queryUrl;
	private SelectBoxModel fieldSelectBox;
	private SortInfoModel sortInfo;
	
	/**
	 * constructor
	 * @param queryUrl 点击查询时提交的url
	 * @param fieldSelectBox 条件字段的数据模型
	 * @param sortInfo 排序字段的数据模型
	 */
	public QueryModelHelper(String queryUrl, SelectBoxModel fieldSelectBox, SortInfoModel sortInfo) {
		Assert.notNull(queryUrl, "queryUrl can not be null");
		this.queryUrl=queryUrl;
		if(fieldSelectBox!=null){
			this.fieldSelectBox=this.wrapWithQueryParamsPrefix(fieldSelectBox);
		}
		if(sortInfo!=null){
			this.sortInfo=sortInfo;
		}
	}
	
	private SelectBoxModel wrapWithQueryParamsPrefix(SelectBoxModel fieldSelectBox){
		SelectBoxModel sbm=new SelectBoxModel();
		List<SelectBoxModel.Option> options=fieldSelectBox.getOptions();
		String key=null, prefix=this.getQueryParamsPrefix();
		for(SelectBoxModel.Option op : options){
			key=op.key.startsWith(prefix) ? op.key : prefix+op.key;
			sbm.addOption(key, op.value, op.selected);
		}
		return sbm;
	}
	
	/**
	 * 从request请求对象中获取并处理查询和排序参数，生成QuerySpecs对象，并和简单查询partial相关的数据模型写入model对象
	 * @param model
	 * @param request
	 * @return
	 */
	public QuerySpecs populateQueryModel(Model model, HttpServletRequest request){
		Assert.notNull(model, "model can not be null");
		Assert.notNull(request, "request can not be null");
		
		QuerySpecs querySpecs=new QuerySpecs();
		Map<String, Object> searchParams = Servlets.getParametersStartingWith(request, queryParamsPrefix);
		if(!searchParams.isEmpty()){
			querySpecs.setFiltersByParamsMap(searchParams);
		}
		
		Map<String, Object> sortParams = Servlets.getParametersStartingWith(request, sortParamsPrefix);
		if(!sortParams.isEmpty()){
			querySpecs.setOrdersByParamsMap(sortParams);
			Map.Entry<String, Object> entry=sortParams.entrySet().iterator().next();
			sortInfo.setForCurrent(entry.getKey(), QueryOrder.Direction.valueOf(entry.getValue().toString()));
		}
		if(this.sortInfo!=null){
			model.addAttribute("sortInfo", sortInfo);
		}

		final String queryField=request.getParameter("queryField");
		final String queryValue=request.getParameter("queryValue");
		if(this.fieldSelectBox!=null){
			if(StringUtils.isNotBlank(queryField)){
				fieldSelectBox.setOptionSelected(queryField);
			}
			else{
				fieldSelectBox.setFirstOptionSelected();
			}
			model.addAttribute("queryFields", fieldSelectBox);
		}
		
		model.addAttribute("queryValue", queryValue);
		model.addAttribute("queryUrl", queryUrl);
		Map<String, String> otherParams=this.getOtherParams(model, request);
		model.addAttribute("otherParams", otherParams.entrySet());
		model.addAttribute("filterExp", compositeFilterExpression(querySpecs, model, request));
		for(QueryFilter qf : querySpecs.getFilters()){
			model.addAttribute(qf.getFieldName(), qf.getValue());
		}
		model.addAttribute(querySpecs);
		return querySpecs;
	}
	
	private Map<String, String> getOtherParams(Model model, HttpServletRequest request){
		final String[] reservedParams={"sortInfo","queryFields","queryField","queryValue","queryUrl",
				"otherParams","filterExp","page"};
		Map<String, String> otherParams=Maps.newHashMap();
		@SuppressWarnings("unchecked")
		Map<String, String[]> paramsMap=request.getParameterMap();
		for(Map.Entry<String, String[]> entry : paramsMap.entrySet()){
			final String key=entry.getKey();
			if(!key.startsWith(this.queryParamsPrefix) 
					&& !key.startsWith(this.sortParamsPrefix)
					&& !ArrayUtils.contains(reservedParams, key)
					&& !model.containsAttribute(key)){
				final String[] values=entry.getValue();
				if(values.length==1){
					otherParams.put(key, values[0]);
				}
				else if(values.length>1){
					StringBuilder sb=new StringBuilder(values[0]);
					for(int i=0; i<values.length; i++){
						sb.append(',').append(values[i]);
					}
					otherParams.put(key, sb.toString());
				}
			}
		}
		return otherParams;
	}
	
	private String compositeFilterExpression(QuerySpecs querySpecs, Model model, HttpServletRequest request){
		StringBuilder sb=new StringBuilder();
		final char joinChar='&';
		for(QueryFilter qf : querySpecs.getFilters()){
			sb.append(queryParamsPrefix+qf.getFilterExpression()).append(joinChar);
		}
		Map<String, String> otherParams=this.getOtherParams(model, request);
		for(Map.Entry<String, String> entry : otherParams.entrySet()){
			sb.append(entry.getKey()).append('=').append(entry.getValue()).append(joinChar);
		}
		String queryValue=request.getParameter("queryValue");
		if(StringUtils.isNotBlank(queryValue)){
			if(sb.charAt(sb.length()-1)!=joinChar){
				sb.append(joinChar);
			}
			sb.append("queryValue=").append(queryValue);
		}
		if(sb.length()>0 && sb.charAt(sb.length()-1)==joinChar){
			sb.deleteCharAt(sb.length()-1);
		}
		return sb.toString();
	}

	/**
	 * @return the queryParamsPrefix
	 */
	public String getQueryParamsPrefix() {
		return queryParamsPrefix;
	}

	/**
	 * @param queryParamsPrefix the queryParamsPrefix to set
	 */
	public void setQueryParamsPrefix(String queryParamsPrefix) {
		this.queryParamsPrefix = queryParamsPrefix;
	}

	/**
	 * @return the sortParamsPrefix
	 */
	public String getSortParamsPrefix() {
		return sortParamsPrefix;
	}

	/**
	 * @param sortParamsPrefix the sortParamsPrefix to set
	 */
	public void setSortParamsPrefix(String sortParamsPrefix) {
		this.sortParamsPrefix = sortParamsPrefix;
	}

	/**
	 * @return the queryUrl
	 */
	public String getQueryUrl() {
		return queryUrl;
	}

	/**
	 * @return the fieldSelectBox
	 */
	public SelectBoxModel getFieldSelectBox() {
		return fieldSelectBox;
	}

	/**
	 * @return the sortInfo
	 */
	public SortInfoModel getSortInfo() {
		return sortInfo;
	}
}
